//
//  BridgeScrollViewController.swift
//  BridgeScrollView
//
//  Created by zjkj on 2024/8/2.
//

import UIKit
import Combine
import SwiftUI

class BridgeScrollViewController<Content>: UIHostingController<Content>, UIScrollViewDelegate where Content: View {
    
    /// 当前滚动配置
    private var configuration: ObservedObject<BridgeScrollConfiguration>
    
    /// 当前ScrollView
    private var scrollView: UIScrollView? {
        didSet {
            /// 找到这个ScrollView的时候先拿一次contentSize，因为这个时候已经有值
            if let scrollView {
                configuration.wrappedValue.updateContentSize(scrollView.contentSize)
                if scrollView.bounds.size.height < scrollView.contentSize.height || scrollView.bounds.size.width < scrollView.contentSize.width {
                    configuration.wrappedValue.updateIsFull(true)
                }else{
                    configuration.wrappedValue.updateIsFull(false)
                }
            }
        }
    }
    /// 上一次的Offset
    private var lastOffset: CGPoint = .zero
    /// contentSize变化监听
    private var contentSizeObservation: NSKeyValueObservation?
    /// 高度变化监听
    private var heightObservation: NSKeyValueObservation?
    /// 接收是否分页滑动通知
    private var pageAnyCancellable: AnyCancellable?
    /// 接收是否有回弹效果通知
    private var bouncsAnyCancellable: AnyCancellable?
    
    /// 初始化方法
    /// - Parameters:
    ///   - configuration: 配置
    ///   - contentView: ScrollView
    init(configuration: BridgeScrollConfiguration, contentView: Content) {
        self.configuration = ObservedObject(wrappedValue: configuration)
        super.init(rootView: contentView)
    }
    
    @MainActor required dynamic init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    override func viewDidAppear(_ animated: Bool) {
        contentSizeObservation = nil
        heightObservation = nil
        pageAnyCancellable = nil
        bouncsAnyCancellable = nil
        scrollView = findCurrentScrollView(in: view)
        scrollView?.delegate = self
        addScrollViewObserver()
        super.viewDidAppear(animated)
    }
    
    func freshContentView(_ contentView: Content) {
        rootView = contentView
    }
    
    /// 添加通知
    private func addScrollViewObserver() {
        contentSizeObservation = scrollView?.observe(\.contentSize, options: [.old, .new]) {[weak self] s, c in
            if c.newValue != c.oldValue {
                self?.configuration.wrappedValue.updateContentSize(s.contentSize)
                if s.frame.height < s.contentSize.height || s.frame.width < s.contentSize.width {
                    self?.configuration.wrappedValue.updateIsFull(true)
                }else{
                    self?.configuration.wrappedValue.updateIsFull(false)
                }
            }
        }
        heightObservation = scrollView?.observe(\.frame, options: [.old, .new], changeHandler: {[weak self] s, c in
            if let newValue = c.newValue, newValue.size != c.oldValue?.size {
                if newValue.height < s.contentSize.height || newValue.size.width < s.contentSize.width {
                    self?.configuration.wrappedValue.updateIsFull(true)
                }else{
                    self?.configuration.wrappedValue.updateIsFull(false)
                }
            }
        })
        /// 设置通知
        pageAnyCancellable = self.configuration.wrappedValue.$isPagingEnable.sink(receiveValue: {[weak self] value in
            guard let self = self else { return }
            self.scrollView?.isPagingEnabled = value
        })
        bouncsAnyCancellable = self.configuration.wrappedValue.$isBounces.sink(receiveValue: {[weak self] value in
            guard let self = self else { return }
            self.scrollView?.bounces = value
        })
        /// 实现滑动到指定位置Block
        configuration.wrappedValue.scrollToOffset = {[weak self] offset, animated in
            guard let self = self else { return }
            self.scrollTo(offset, animated: animated)
        }
    }
    
    /// 滚动到指定位置
    /// - Parameters:
    ///   - offset: offset
    ///   - animated: 是否执行滚动动画
    func scrollTo(_ offset: CGPoint, animated: Bool = true) {
        scrollView?.setContentOffset(offset, animated: animated)
    }
    
    
    func scrollViewDidScroll(_ scrollView: UIScrollView) {
        let offset = scrollView.contentOffset
        ///更新滚动状态
        if !configuration.wrappedValue.isScrolling {
            configuration.wrappedValue.updateIsScrolling(true)
        }
        /// 如果不是惯性滚动，更新一下滚动方向，因为有回弹的效果所以，回弹的时候就会反方向，所以直接判断拖动中的方向
        if !configuration.wrappedValue.isDecelerating {
            var scrollDirection: BridgeScrollConfiguration.Direction = .static
            if offset.x > lastOffset.x {
                if offset.y > lastOffset.y {
                    scrollDirection = .leftTop
                }else if offset.y == lastOffset.y {
                    scrollDirection = .left
                }else{
                    scrollDirection = .leftbottom
                }
            }else if offset.x == lastOffset.x{
                if offset.y > lastOffset.y {
                    scrollDirection = .top
                }else if offset.y == lastOffset.y {
                    /// 静止状态，但是没放手
                }else{
                    scrollDirection = .bottom
                }
            }else{
                if offset.y > lastOffset.y {
                    scrollDirection = .rightTop
                }else if offset.y == lastOffset.y {
                    scrollDirection = .right
                }else{
                    scrollDirection = .rightBottom
                }
            }
            configuration.wrappedValue.updateScrollType(scrollDirection)
        }
        lastOffset = offset
        configuration.wrappedValue.updateContentOffset(scrollView.contentOffset)
        let boundsSize = scrollView.bounds.size
        let currentSize = scrollView.contentSize
        let inset = scrollView.adjustedContentInset
        
        /// 判断是否在底部，兼容了横向和纵向
        if offset.y - (currentSize.height + inset.bottom - boundsSize.height) + configuration.wrappedValue.ignoreInsets.bottom >= 0 && offset.x - (currentSize.width + inset.right - boundsSize.width) + configuration.wrappedValue.ignoreInsets.right >= 0  {
            if !configuration.wrappedValue.isBottom {
                configuration.wrappedValue.updateIsBottom(true)
            }
        }else{
            if configuration.wrappedValue.isBottom {
                configuration.wrappedValue.updateIsBottom(false)
            }
        }
        /// 判断是否在顶部，兼容了横向和纵向
        if offset.y + inset.top + configuration.wrappedValue.ignoreInsets.top <= 0 && offset.x + inset.left + configuration.wrappedValue.ignoreInsets.left <= 0 {
            if !configuration.wrappedValue.isTop {
                configuration.wrappedValue.updateIsTop(true)
            }
        }else{
            if configuration.wrappedValue.isTop {
                configuration.wrappedValue.updateIsTop(false)
            }
        }
    }
    
    func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
        /// 更新拖拽状态
        if !configuration.wrappedValue.isDragging {
            configuration.wrappedValue.updateIsDrag(true)
        }
        /// 更新用户拖动
        configuration.wrappedValue.updateIsUserScroll(true)
    }
    
    func scrollViewWillBeginDecelerating(_ scrollView: UIScrollView) {
        /// 更新惯性滚动状态
        if !configuration.wrappedValue.isDecelerating {
            configuration.wrappedValue.updateIsDecelerating(true)
        }
    }
    
    /// 人为拖拽scrollView导致滚动完毕，才会调用这个方法
    func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
        if configuration.wrappedValue.isDecelerating {
            configuration.wrappedValue.updateIsDecelerating(false)
        }
        if configuration.wrappedValue.isScrolling {
            configuration.wrappedValue.updateIsScrolling(false)
            configuration.wrappedValue.updateScrollType(.static)
        }
        configuration.wrappedValue.updateIsUserScroll(false)
    }
    
    func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
        if configuration.wrappedValue.isDragging {
            configuration.wrappedValue.updateIsDrag(false)
        }
        if !decelerate {
            if configuration.wrappedValue.isScrolling {
                configuration.wrappedValue.updateIsScrolling(false)
                configuration.wrappedValue.updateScrollType(.static)
            }
            configuration.wrappedValue.updateIsUserScroll(false)
        }
    }
    
    /// 不是人为拖拽scrollView导致滚动完毕，才会调用这个方法
    func scrollViewDidEndScrollingAnimation(_ scrollView: UIScrollView) {
        if configuration.wrappedValue.isScrolling {
            configuration.wrappedValue.updateIsScrolling(false)
            configuration.wrappedValue.updateScrollType(.static)
        }
    }
    
    func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) {
        
    }
    
    func scrollViewWillBeginZooming(_ scrollView: UIScrollView, with view: UIView?) {
        if !configuration.wrappedValue.isZooming {
            configuration.wrappedValue.updateIsZooming(true)
        }
    }
    
    func scrollViewDidEndZooming(_ scrollView: UIScrollView, with view: UIView?, atScale scale: CGFloat) {
        if configuration.wrappedValue.isZooming {
            configuration.wrappedValue.updateIsZooming(false)
        }
        configuration.wrappedValue.updateZoomLevel(scale)
    }
    
}

extension BridgeScrollViewController {
    
    /// 找到当前ScrollView
    /// - Parameter view: view
    /// - Returns: ScrollView
    private func findCurrentScrollView(in view: UIView) -> UIScrollView? {
        if view.isKind(of: UIScrollView.self) {
            return view as? UIScrollView
        }
        
        for subview in view.subviews {
            if let subScroll = findCurrentScrollView(in: subview) {
                return subScroll
            }
        }
        return nil
    }
}
